Skip to content

Fix unsigned wrap in bzdecompress() output realloc at source_len UINT_MAX#22432

Merged
iliaal merged 1 commit into
php:masterfrom
iliaal:bz2-decompress-uintmax-wrap
Jun 25, 2026
Merged

Fix unsigned wrap in bzdecompress() output realloc at source_len UINT_MAX#22432
iliaal merged 1 commit into
php:masterfrom
iliaal:bz2-decompress-uintmax-wrap

Conversation

@iliaal

@iliaal iliaal commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

The input guard rejects only source_len > UINT_MAX, so source_len == UINT_MAX is permitted and assigned to bzs.avail_out (unsigned int). The per-iteration realloc then computed bzs.avail_out+1 in unsigned int arithmetic, which wraps to 0 at UINT_MAX, allocating no headroom while bz2 still believes avail_out bytes are available at next_out, so the next decompress round writes past the buffer. Compute the term as (size_t)bzs.avail_out + 1 so the increment is done in size_t and cannot wrap, matching the (size_t) casts already on the same call. Triggering needs a ~4GB crafted input, so there is no portable red/green test. Follow-up to the input guard added in #22242.

…_MAX

The input guard rejects only source_len > UINT_MAX, so source_len ==
UINT_MAX is permitted and assigned to bzs.avail_out (unsigned int). The
per-iteration realloc then computed bzs.avail_out+1 in unsigned int
arithmetic, which wraps to 0 at UINT_MAX, allocating no headroom while
bz2 still believes avail_out bytes are available at next_out: the next
decompress round writes past the buffer. Compute the term as
(size_t)bzs.avail_out + 1 so the increment is done in size_t and cannot
wrap, matching the (size_t) casts already used on the same call.
@iliaal iliaal merged commit 15d58ca into php:master Jun 25, 2026
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants